namespace RAYTRACING {
    let help_uint8Arr: Uint8Array = new Uint8Array(1);

    /**
    * Uint8Array转成 CSS 颜色格式
    * @param uint8Arr 存放颜色的buffer [r,g,b,a]
    * @returns like #ffffffff
    */
    export function Uint8ArraytoCSS(uint8Arr: Uint8Array) {
        let r = uint8Arr[0].toString(16);
        let g = uint8Arr[1].toString(16);
        let b = uint8Arr[2].toString(16);
        if (r.length == 1) r = "0" + r;
        if (g.length == 1) g = "0" + g;
        if (b.length == 1) b = "0" + b;
        if (uint8Arr.length >= 4) {
            let a = uint8Arr[3].toString(16);
            if (a.length == 1) a = "0" + a;
            return `#${r}${g}${b}${a}`;
        } else {
            return `#${r}${g}${b}`;
        }
    }

    /** 线性差值 */
    export function NumberLerp(a: number, b: number, value: number) {
        return a * (1 - value) + b * value;
    }

    export function Clamp(src: number, min: number, max: number): number {
        return src < min ? min : src > max ? max : src;
    }

    /** 角度转到弧度 系数 */
    export const TO_RADIAN = 0.0174532924;
    /** 弧度转到角度 系数 */
    export const TO_ANGLE = 57.29578;
    export class Vector2 implements IClone {
        constructor(public x = 0, public y = 0) {
        }

        public clone() {
            return new Vector2(this.x, this.y);
        }

        public copy(src: Vector2) {
            this.x = src.x;
            this.y = src.y;
        }

        public get length() {
            let sLen = this.x * this.x + this.y * this.y;
            return Math.sqrt(sLen);
        }

        public set(x: number, y: number) {
            this.x = x;
            this.y = y;
        }
    }

    export class Vector3 implements IClone {
        constructor(public x = 0, public y = 0, public z = 0) {
        }

        public clone() {
            return new Vector3(this.x, this.y, this.z);
        }

        public copy(src: Vector3) {
            this.x = src.x;
            this.y = src.y;
            this.z = src.z;
        }

        public get length() {
            let sLen = this.x * this.x + this.y * this.y + this.z * this.z;
            return Math.sqrt(sLen);
        }

        /** 是否相等 */
        public equa(a: Vector3) {
            if (this.x != a.x) return false;
            if (this.y != a.y) return false;
            if (this.z != a.z) return false;
            return true;
        }

        public set(x: number, y: number, z: number) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public static Sub(a: Vector3, b: Vector3, out: Vector3) {
            out.x = a.x - b.x;
            out.y = a.y - b.y;
            out.z = a.z - b.z;
        }

        public static Add(a: Vector3, b: Vector3, out: Vector3) {
            out.x = a.x + b.x;
            out.y = a.y + b.y;
            out.z = a.z + b.z;
        }

        public static Normalize(src: Vector3, out: Vector3) {
            let len = src.length;
            out.x = src.x / len;
            out.y = src.y / len;
            out.z = src.z / len;
        }

        public static Lerp(a: Vector3, b: Vector3, v: number, out: Vector3) {
            out.x = a.x * (1 - v) + b.x * v;
            out.y = a.y * (1 - v) + b.y * v;
            out.z = a.z * (1 - v) + b.z * v;
        }

        public static Distance(a: Vector3, b: Vector3) {
            let x = a.x - b.x;
            let y = a.y - b.y;
            let z = a.z - b.z;
            return Math.sqrt(x * x + y * y + z * z);
        }

        public static DistanceSqr(a: Vector3, b: Vector3) {
            let x = a.x - b.x;
            let y = a.y - b.y;
            let z = a.z - b.z;
            return x * x + y * y + z * z;
        }

        public static Cross(left: Vector3, right: Vector3, out: Vector3) {
            let x = left.y * right.z - left.z * right.y;
            let y = left.z * right.x - left.x * right.z;
            let z = left.x * right.y - left.y * right.x;
            out.set(x, y, z);
        }

        public static Dot(a: Vector3, b: Vector3) {
            return a.x * b.x + a.y * b.y + a.z * b.z;
        }

        public static ScaleByNum(src: Vector3, scale: number, out: Vector3) {
            out.x = src.x * scale;
            out.y = src.y * scale;
            out.z = src.z * scale;
        }

        public static ScaleByV3(src: Vector3, scale: Vector3, out: Vector3) {
            out.x = src.x * scale.x;
            out.y = src.y * scale.y;
            out.z = src.z * scale.z;
        }

        public static Reflect(In: Vector3, N: Vector3, out: Vector3) {
            //out = In + 2(-In.N)*N
            let val = Vector3.Dot(In, N) * -2;
            // Vector3.ScaleByNum(N, val, out);
            // Vector3.Add(out, In, out);
            let x = val * N.x + In.x;
            let y = val * N.y + In.y;
            let z = val * N.z + In.z;
            out.set(x, y, z);
        }

        /**
         * 随机一个在单位球内的向量
         * @param out 
         */
        public static RandomInUnitSphere(out: Vector3) {
            do {
                out.set(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1);
            } while (Vector3.Dot(out, out) >= 1);   //不在单位球内 继续循环
        }

        /**
         * 随机一个单位向量
         * @param out 
         */
        public static RandomUnit(out: Vector3) {
            Vector3.RandomInUnitSphere(out);
            Vector3.Normalize(out, out);
        }
    }

    export class Quaternion {
        constructor(public x = 0, public y = 0, public z = 0, public w = 1) {
        }

        public clone() {
            return new Quaternion(this.x, this.y, this.z, this.w);
        }

        public copy(src: Quaternion) {
            this.x = src.x;
            this.y = src.y;
            this.z = src.z;
            this.w = src.w;
        }

        /** 是否相等 */
        public equa(a: Quaternion) {
            if (this.x != a.x) return false;
            if (this.y != a.y) return false;
            if (this.z != a.z) return false;
            if (this.w != a.w) return false;
            return true;
        }

        public static Normalize(src: Quaternion, out: Quaternion) {
            let mag = 1 / Math.sqrt(src.x * src.x + src.y * src.y + src.z * src.z + src.w * src.w);
            out.x *= mag;
            out.y *= mag;
            out.z *= mag;
            out.w *= mag;
        }

        public static TransformVec3(src: Quaternion, v3: Vector3, out: Vector3) {
            let x2 = v3.x, y2 = v3.y, z2 = v3.z;
            let w1 = -src.x * x2 - src.y * y2 - src.z * z2;
            let x1 = src.w * x2 + src.y * z2 - src.z * y2;
            let y1 = src.w * y2 - src.x * z2 + src.z * x2;
            let z1 = src.w * z2 + src.x * y2 - src.y * x2;

            out.x = -w1 * src.x + x1 * src.w - y1 * src.z + z1 * src.y;
            out.y = -w1 * src.y + x1 * src.z + y1 * src.w - z1 * src.x;
            out.z = -w1 * src.z - x1 * src.y + y1 * src.x + z1 * src.w;
        }

        /**
         * 通过 欧拉角设置四元数
         * @param ax X 轴的角度
         * @param ay Y 轴的角度
         * @param az Z 轴的角度
         * @param out 
         */
        public static SetByEuler(ax: number, ay: number, az: number, out: Quaternion) {
            ax *= TO_RADIAN;
            ay *= TO_RADIAN;
            az *= TO_RADIAN;

            var halfX: number = ax * 0.5, halfY: number = ay * 0.5, halfZ: number = az * 0.5;
            var cosX: number = Math.cos(halfX), sinX: number = Math.sin(halfX);
            var cosY: number = Math.cos(halfY), sinY: number = Math.sin(halfY);
            var cosZ: number = Math.cos(halfZ), sinZ: number = Math.sin(halfZ);

            out.w = cosX * cosY * cosZ + sinX * sinY * sinZ;
            out.x = sinX * cosY * cosZ + cosX * sinY * sinZ;
            out.y = cosX * sinY * cosZ - sinX * cosY * sinZ;
            out.z = cosX * cosY * sinZ - sinX * sinY * cosZ;

            Quaternion.Normalize(out, out);
        }


    }

    // export class Rect {
    //     constructor(public x: number = 0, public y: number = 0, public w: number = 0, public h: number = 0) {
    //     }
    // }

    // export class matrix {

    // }

    export class Color32 implements IClone {
        public static White() { return new Color32() }
        public static Grey() { return new Color32(128, 128, 128, 255) }
        public static Black() { return new Color32(0, 0, 0, 255) }
        public static Red() { return new Color32(255, 0, 0, 255) }
        public static Green() { return new Color32(0, 255, 0, 255) }
        public static Blue() { return new Color32(0, 0, 255, 255) }

        constructor(public r: number = 255, public g: number = 255, public b: number = 255, public a: number = 255) {
        }

        public clone() {
            return new Color32(this.r, this.g, this.b, this.a);
        }

        public copy(src: Color32) {
            this.r = src.r;
            this.g = src.g;
            this.b = src.b;
            this.a = src.a;
        }

        public set(r: number, g: number, b: number, a: number) {
            this.r = r;
            this.g = g;
            this.b = b;
            this.a = a;
        }

        /**
         * 颜色转成 CSS 格式
         * @param hasAlpha 是否包含Alpha
         * @returns like #ffffffff
         */
        public toCSS(hasAlpha = true) {
            let src = this;
            let r = src.r.toString(16);
            let g = src.g.toString(16);
            let b = src.b.toString(16);
            if (r.length == 1) r = "0" + r;
            if (g.length == 1) g = "0" + g;
            if (b.length == 1) b = "0" + b;
            if (hasAlpha) {
                let a = src.a.toString(16);
                if (a.length == 1) a = "0" + a;
                return `#${r}${g}${b}${a}`;
            } else {
                return `#${r}${g}${b}`;
            }
        }
    }

    export class Color implements IClone {
        public static White() { return new Color() }
        public static Grey() { return new Color(0.5, 0.5, 0.5, 1) }
        public static Black() { return new Color(0, 0, 0, 1) }
        public static Red() { return new Color(1, 0, 0, 1) }
        public static Green() { return new Color(0, 1, 0, 1) }
        public static Blue() { return new Color(0, 0, 1, 1) }
        constructor(public r: number = 1, public g: number = 1, public b: number = 1, public a: number = 1) {
        }

        public clone() {
            return new Color(this.r, this.g, this.b, this.a);
        }

        public copy(src: Color) {
            this.r = src.r;
            this.g = src.g;
            this.b = src.b;
            this.a = src.a;
        }

        public set(r: number, g: number, b: number, a: number) {
            this.r = r;
            this.g = g;
            this.b = b;
            this.a = a;
        }

        /**
         * 颜色转成 CSS 格式
         * @param hasAlpha 是否包含Alpha
         * @returns like #ffffffff
         */
        public toCSS(hasAlpha = true) {
            let src = this;
            let r = Math.round(src.r * 255).toString(16);
            let g = Math.round(src.g * 255).toString(16);
            let b = Math.round(src.b * 255).toString(16);
            if (r.length == 1) r = "0" + r;
            if (g.length == 1) g = "0" + g;
            if (b.length == 1) b = "0" + b;
            if (hasAlpha) {
                let a = Math.round(src.a * 255).toString(16);
                if (a.length == 1) a = "0" + a;
                return `#${r}${g}${b}${a}`;
            } else {
                return `#${r}${g}${b}`;
            }
        }

        public static Add(a: Color, b: Color, out: Color) {
            out.r = a.r + b.r;
            out.g = a.g + b.g;
            out.b = a.b + b.b;
            out.a = a.a + b.a;
        }

        public static Scale(src: Color, scale: number, out: Color) {
            out.r = src.r * scale;
            out.g = src.g * scale;
            out.b = src.b * scale;
            out.a = src.a * scale;
        }

        public static Mult(a: Color, b: Color, out: Color) {
            out.r = a.r * b.r;
            out.g = a.g * b.g;
            out.b = a.b * b.b;
            out.a = a.a * b.a;
        }

        /**
         * ACES 映射 ，HDR to LDR
         * @param src 
         * @param out 
         */
        public static toneMapACES(src: Color, out: Color) {
            const A = 2.51;
            const B = 0.03;
            const C = 2.43;
            const D = 0.59;
            const E = 0.14;
            const GAMMA = 2.2;
            let nGAMMA = 1 / GAMMA;
            out.r = Math.pow(Clamp((src.r * (A * src.r + B)) / (src.r * (C * src.r + D) + E), 0, 1), nGAMMA);
            out.g = Math.pow(Clamp((src.g * (A * src.g + B)) / (src.g * (C * src.g + D) + E), 0, 1), nGAMMA);
            out.b = Math.pow(Clamp((src.b * (A * src.b + B)) / (src.b * (C * src.b + D) + E), 0, 1), nGAMMA);
        }
    }

    export class IntersectionInfo {
        public faceId = 0;
        // public subMeshId = 0;
        public bu = 0;
        public bv = 0;
        public distance = 0;

        public setBase(bu: number, bv: number, distance: number) {
            this.bu = bu;
            this.bv = bv;
            this.distance = distance;
        }

        public clone(info: IntersectionInfo) {
            this.faceId = info.faceId;
            // this.subMeshId = info.subMeshId;
            this.bu = info.bu;
            this.bv = info.bv;
            this.distance = info.distance;
        }
    }

    export class Ray implements IClone {
        private static readonly _edge1 = new Vector3();
        private static readonly _edge2 = new Vector3();
        private static readonly _pvec = new Vector3();
        private static readonly _tvec = new Vector3();
        private static readonly _qvec = new Vector3();

        origin: Vector3 = new Vector3();
        direction: Vector3 = new Vector3();

        copy(src: Ray): void {
            this.origin.copy(src.origin);
            this.direction.copy(src.direction);
        }

        clone() {
            let _ray = new Ray();
            _ray.copy(this);
            return _ray;
        }

        /**
         * 射线与 三角形相交检测
         * @param p0 三角形点0
         * @param p1 三角形点1
         * @param p2 三角形点2
         * @param outInfo 输出相交信息
         * @returns 是否相交
         */
        public intersectsTriangle(p0: Vector3, p1: Vector3, p2: Vector3, outInfo: IntersectionInfo): boolean {
            let _edge1 = Ray._edge1;
            let _edge2 = Ray._edge2;
            let _pvec = Ray._pvec;
            let _tvec = Ray._tvec;
            let _qvec = Ray._qvec;

            Vector3.Sub(p1, p0, _edge1);
            Vector3.Sub(p2, p0, _edge2);
            Vector3.Cross(this.direction, _edge2, _pvec);
            let dot = Vector3.Dot(_edge1, _pvec);
            if (dot == 0) return false;

            let invdot = 1 / dot;
            Vector3.Sub(this.origin, p0, _tvec);
            let bu = Vector3.Dot(_tvec, _pvec) * invdot;
            if (bu < 0 || bu > 1) return false;

            Vector3.Cross(_tvec, _edge1, _qvec);
            let bv = Vector3.Dot(this.direction, _qvec) * invdot;
            if (bv < 0 || bu + bv > 1) return false;

            let dist = Vector3.Dot(_edge2, _qvec) * invdot;
            outInfo.setBase(bu, bv, dist);
            return true;
        }
    }

}